# engine/_row_cy.py
# Copyright (C) 2010-2025 the SQLAlchemy authors and contributors
# <see AUTHORS file>
#
# This module is part of SQLAlchemy and is released under
# the MIT License: https://www.opensource.org/licenses/mit-license.php
# mypy: disable-error-code="misc,no-redef,valid-type,no-untyped-call"
# mypy: disable-error-code="index,no-any-return,arg-type,assignment"
from __future__ import annotations

from typing import Any
from typing import Dict
from typing import Iterator
from typing import List
from typing import NoReturn
from typing import Optional
from typing import Sequence
from typing import Tuple
from typing import Type
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from .result import _KeyType
    from .result import _ProcessorsType
    from .result import ResultMetaData

# START GENERATED CYTHON IMPORT
# This section is automatically generated by the script tools/cython_imports.py
try:
    # NOTE: the cython compiler needs this "import cython" in the file, it
    # can't be only "from sqlalchemy.util import cython" with the fallback
    # in that module
    import cython
except ModuleNotFoundError:
    from sqlalchemy.util import cython


def _is_compiled() -> bool:
    """Utility function to indicate if this module is compiled or not."""
    return cython.compiled  # type: ignore[no-any-return,unused-ignore]


# END GENERATED CYTHON IMPORT


@cython.cclass
class BaseRow:
    __slots__ = ("_parent", "_data", "_key_to_index")

    if cython.compiled:
        _parent: ResultMetaData = cython.declare(object, visibility="readonly")
        _key_to_index: Dict[_KeyType, int] = cython.declare(
            dict, visibility="readonly"
        )
        _data: Tuple[Any, ...] = cython.declare(tuple, visibility="readonly")

    def __init__(
        self,
        parent: ResultMetaData,
        processors: Optional[_ProcessorsType],
        key_to_index: Dict[_KeyType, int],
        data: Sequence[Any],
    ) -> None:
        """Row objects are constructed by CursorResult objects."""
        self._set_attrs(
            parent,
            key_to_index,
            (
                _apply_processors(processors, data)
                if processors is not None
                else data if isinstance(data, tuple) else tuple(data)
            ),
        )

    @cython.cfunc
    @cython.inline
    def _set_attrs(  # type: ignore[no-untyped-def] # cython crashes
        self,
        parent: ResultMetaData,
        key_to_index: Dict[_KeyType, int],
        data: Tuple[Any, ...],
    ):
        if cython.compiled:
            # cython does not use __setattr__
            self._parent = parent
            self._key_to_index = key_to_index
            self._data = data
        else:
            # python does, so use object.__setattr__
            object.__setattr__(self, "_parent", parent)
            object.__setattr__(self, "_key_to_index", key_to_index)
            object.__setattr__(self, "_data", data)

    def __reduce__(self) -> Tuple[Any, Any]:
        return (
            rowproxy_reconstructor,
            (self.__class__, self.__getstate__()),
        )

    def __getstate__(self) -> Dict[str, Any]:
        return {"_parent": self._parent, "_data": self._data}

    def __setstate__(self, state: Dict[str, Any]) -> None:
        parent = state["_parent"]
        self._set_attrs(parent, parent._key_to_index, state["_data"])

    def _values_impl(self) -> List[Any]:
        return list(self._data)

    def __iter__(self) -> Iterator[Any]:
        return iter(self._data)

    def __len__(self) -> int:
        return len(self._data)

    def __hash__(self) -> int:
        return hash(self._data)

    if not TYPE_CHECKING:

        def __getitem__(self, key: Any) -> Any:
            return self._data[key]

    def _get_by_key_impl_mapping(self, key: _KeyType) -> Any:
        return self._get_by_key_impl(key, False)

    @cython.cfunc
    @cython.inline
    def _get_by_key_impl(self, key: _KeyType, attr_err: cython.bint) -> object:
        # NOTE: don't type index since there is no advantage in making cython
        # do a type check
        index = self._key_to_index.get(key)
        if index is not None:
            return self._data[index]
        self._parent._key_not_found(key, attr_err)

    if cython.compiled:

        @cython.annotation_typing(False)
        def __getattribute__(self, name: str) -> Any:
            # this optimizes getattr access on cython, that's otherwise
            # quite slow compared with python. The assumption is that
            # most columns will not start with _. If they do they will
            # fallback on __getattr__ in any case.
            if name != "" and name[0] != "_":
                # inline of _get_by_key_impl. Attribute on the class
                # take precedence over column names.
                index = self._key_to_index.get(name)
                if index is not None and not hasattr(type(self), name):
                    return self._data[index]

            return object.__getattribute__(self, name)

    @cython.annotation_typing(False)
    def __getattr__(self, name: str) -> Any:
        return self._get_by_key_impl(name, True)

    def __setattr__(self, name: str, value: Any) -> NoReturn:
        raise AttributeError("can't set attribute")

    def __delattr__(self, name: str) -> NoReturn:
        raise AttributeError("can't delete attribute")

    def _to_tuple_instance(self) -> Tuple[Any, ...]:
        return self._data

    def __contains__(self, key: Any) -> cython.bint:
        return key in self._data


if cython.compiled:

    from cython.cimports.cpython import PyTuple_New
    from cython.cimports.cpython import Py_INCREF
    from cython.cimports.cpython import PyTuple_SET_ITEM

    @cython.inline
    @cython.cfunc
    @cython.wraparound(False)
    @cython.boundscheck(False)
    @cython.locals(
        res=tuple,
        proc_size=cython.Py_ssize_t,
        i=cython.Py_ssize_t,
        p=object,
        value=object,
    )
    def _apply_processors(
        proc: Sequence[Any], data: Sequence[Any]
    ) -> Tuple[Any, ...]:
        proc_size = len(proc)
        # TODO: would be nice to do this only on the fist row
        assert len(data) == proc_size
        res = PyTuple_New(proc_size)
        for i in range(proc_size):
            p = proc[i]
            if p is not None:
                value = p(data[i])
            else:
                value = data[i]
            Py_INCREF(value)
            PyTuple_SET_ITEM(res, i, value)
        return res

else:

    def _apply_processors(
        proc: _ProcessorsType, data: Sequence[Any]
    ) -> Tuple[Any, ...]:
        res: List[Any] = list(data)
        proc_size = len(proc)
        # TODO: would be nice to do this only on the fist row
        assert len(res) == proc_size
        for i in range(proc_size):
            p = proc[i]
            if p is not None:
                res[i] = p(res[i])
        return tuple(res)


# This reconstructor is necessary so that pickles with the Cy extension or
# without use the same Binary format.
# Turn off annotation typing so the compiled version accepts the python
# class too.
@cython.annotation_typing(False)
def rowproxy_reconstructor(
    cls: Type[BaseRow], state: Dict[str, Any]
) -> BaseRow:
    obj = cls.__new__(cls)
    obj.__setstate__(state)
    return obj
